/*
 * contrib/citext/citext.c
 */
#include "postgres.h"
#include "knl/knl_variable.h"

#include "access/hash.h"
#include "catalog/pg_collation.h"
#include "utils/builtins.h"
#include "utils/formatting.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/*
 *		====================
 *		FORWARD DECLARATIONS
 *		====================
 */

static int32 citextcmp(text* left, text* right, Oid collid);
extern "C" Datum citext_cmp(PG_FUNCTION_ARGS);
extern "C" Datum citext_hash(PG_FUNCTION_ARGS);
extern "C" Datum citext_eq(PG_FUNCTION_ARGS);
extern "C" Datum citext_ne(PG_FUNCTION_ARGS);
extern "C" Datum citext_gt(PG_FUNCTION_ARGS);
extern "C" Datum citext_ge(PG_FUNCTION_ARGS);
extern "C" Datum citext_lt(PG_FUNCTION_ARGS);
extern "C" Datum citext_le(PG_FUNCTION_ARGS);
extern "C" Datum citext_smaller(PG_FUNCTION_ARGS);
extern "C" Datum citext_larger(PG_FUNCTION_ARGS);

/*
 *		=================
 *		UTILITY FUNCTIONS
 *		=================
 */

/*
 * citextcmp()
 * Internal comparison function for citext strings.
 * Returns int32 negative, zero, or positive.
 */
static int32 citextcmp(text* left, text* right, Oid collid)
{
    char *lcstr, *rcstr;
    int32 result;

    /*
     * We must do our str_tolower calls with DEFAULT_COLLATION_OID, not the
     * input collation as you might expect.  This is so that the behavior of
     * citext's equality and hashing functions is not collation-dependent.  We
     * should change this once the core infrastructure is able to cope with
     * collation-dependent equality and hashing functions.
     */

    lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
    rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);

    result = varstr_cmp(lcstr, strlen(lcstr), rcstr, strlen(rcstr), collid);

    pfree(lcstr);
    pfree(rcstr);

    return result;
}

/*
 *		==================
 *		INDEXING FUNCTIONS
 *		==================
 */

PG_FUNCTION_INFO_V1(citext_cmp);

Datum citext_cmp(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    int32 result;

    result = citextcmp(left, right, PG_GET_COLLATION());

    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_INT32(result);
}

PG_FUNCTION_INFO_V1(citext_hash);

Datum citext_hash(PG_FUNCTION_ARGS)
{
    text* txt = PG_GETARG_TEXT_PP(0);
    char* str = NULL;
    Datum result;

    str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID);
    result = hash_any((unsigned char*)str, strlen(str));
    pfree(str);

    /* Avoid leaking memory for toasted inputs */
    PG_FREE_IF_COPY(txt, 0);

    PG_RETURN_DATUM(result);
}

/*
 *		==================
 *		OPERATOR FUNCTIONS
 *		==================
 */

PG_FUNCTION_INFO_V1(citext_eq);

Datum citext_eq(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    char* lcstr = NULL;
    char* rcstr = NULL;
    bool result = false;

    /* We can't compare lengths in advance of downcasing ... */

    lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
    rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);

    /*
     * Since we only care about equality or not-equality, we can avoid all the
     * expense of strcoll() here, and just do bitwise comparison.
     */
    result = (strcmp(lcstr, rcstr) == 0);

    pfree(lcstr);
    pfree(rcstr);
    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_BOOL(result);
}

PG_FUNCTION_INFO_V1(citext_ne);

Datum citext_ne(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    char* lcstr = NULL;
    char* rcstr = NULL;
    bool result = false;

    /* We can't compare lengths in advance of downcasing ... */

    lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
    rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);

    /*
     * Since we only care about equality or not-equality, we can avoid all the
     * expense of strcoll() here, and just do bitwise comparison.
     */
    result = (strcmp(lcstr, rcstr) != 0);

    pfree(lcstr);
    pfree(rcstr);
    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_BOOL(result);
}

PG_FUNCTION_INFO_V1(citext_lt);

Datum citext_lt(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    bool result = false;

    result = citextcmp(left, right, PG_GET_COLLATION()) < 0;

    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_BOOL(result);
}

PG_FUNCTION_INFO_V1(citext_le);

Datum citext_le(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    bool result = false;

    result = citextcmp(left, right, PG_GET_COLLATION()) <= 0;

    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_BOOL(result);
}

PG_FUNCTION_INFO_V1(citext_gt);

Datum citext_gt(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    bool result = false;

    result = citextcmp(left, right, PG_GET_COLLATION()) > 0;

    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_BOOL(result);
}

PG_FUNCTION_INFO_V1(citext_ge);

Datum citext_ge(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    bool result = false;

    result = citextcmp(left, right, PG_GET_COLLATION()) >= 0;

    PG_FREE_IF_COPY(left, 0);
    PG_FREE_IF_COPY(right, 1);

    PG_RETURN_BOOL(result);
}

/*
 *		===================
 *		AGGREGATE FUNCTIONS
 *		===================
 */

PG_FUNCTION_INFO_V1(citext_smaller);

Datum citext_smaller(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    text* result = NULL;

    result = (citextcmp(left, right, PG_GET_COLLATION()) < 0) ? left : right;
    PG_RETURN_TEXT_P(result);
}

PG_FUNCTION_INFO_V1(citext_larger);

Datum citext_larger(PG_FUNCTION_ARGS)
{
    text* left = PG_GETARG_TEXT_PP(0);
    text* right = PG_GETARG_TEXT_PP(1);
    text* result = NULL;

    result = (citextcmp(left, right, PG_GET_COLLATION()) > 0) ? left : right;
    PG_RETURN_TEXT_P(result);
}
